package me.boot.db.mybatis.datasource.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import me.boot.db.mybatis.datasource.DynamicDataSourceService;
import me.boot.db.mybatis.datasource.TenantDataSourceLoader;
import me.boot.db.mybatis.propertites.BasicDataSourceProperties;
import me.boot.db.mybatis.propertites.DbProperties;
import me.boot.db.mybatis.utils.QueryUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.support.JdbcUtils;

import java.sql.*;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static me.boot.common.constant.DbConst.*;

/**
 * @Description:
 * @author: Sylar
 * @date: 2021年11月03日 4:58 下午
 */
@Slf4j
public class DefaultTenantDataSourceLoader implements TenantDataSourceLoader {
    private BasicDataSourceProperties dataSourceProperties;
    private DbProperties dbProperties;

    public DefaultTenantDataSourceLoader(BasicDataSourceProperties dataSourceProperties, DbProperties dbProperties) {
        this.dataSourceProperties = dataSourceProperties;
        this.dbProperties = dbProperties;
    }

    @Override
    public List<DataSourceProperty> load() {

        Connection conn = null;
        Statement stmt = null;
        try {
            // 由于 SPI 的支持，现在已无需显示加载驱动了
            // 但在用户显示配置的情况下，进行主动加载
            if (!StrUtil.isNotEmpty(dataSourceProperties.getDriverClassName())) {
                Class.forName(dataSourceProperties.getDriverClassName());
                log.info("成功加载数据库驱动程序");
            }
            conn = DriverManager.getConnection(
                    dataSourceProperties.getUrl(),
                    dataSourceProperties.getUsername(),
                    dataSourceProperties.getPassword());
            log.info("成功获取数据库连接");
            stmt = conn.createStatement();
            return executeStmt(stmt);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeConnection(conn);
            JdbcUtils.closeStatement(stmt);
        }

        return TenantDataSourceLoader.super.load();
    }

    protected List<DataSourceProperty> executeStmt(Statement statement) throws SQLException {
        DbProperties.Tenant.Datasource.Style style = dbProperties.getTenant().getDatasource().getStyle();

        List<DataSourceProperty> list = Lists.newArrayList();
        if (style.equals(DbProperties.Tenant.Datasource.Style.Column)) {
            list.addAll(queryByColumn(statement));
        } else if (style.equals(DbProperties.Tenant.Datasource.Style.Table)) {
            list.addAll(queryByTable(statement));
        }

        String names = list.stream()
                .sorted(Comparator.comparing(DataSourceProperty::getPoolName))
                .map(DataSourceProperty::getPoolName)
                .collect(Collectors.joining(","));
        log.info("发现租户数据源:{}", names);

        return list;
    }

    protected List<DataSourceProperty> queryByColumn(Statement statement) throws SQLException {
        Assert.isTrue(existTable(statement, TENANT_TABLE_NAME), "未发现租户表:" + TENANT_TABLE_NAME);

        List<DataSourceProperty> list = Lists.newArrayList();
        String sql = StrUtil.format("select {}, {} from {} where {} = 0",
                COLUMN_ID,
                TENANT_DATASOURCE_COLUMN_NAME,
                TENANT_TABLE_NAME,
                COLUMN_DELETED);


        ResultSet rs = statement.executeQuery(sql);
        while (rs.next()) {
            try {
                String id = rs.getString(COLUMN_ID);
                String dsName = DynamicDataSourceService.getTenantDsName(id);
                String dsConfig = rs.getString(TENANT_DATASOURCE_COLUMN_NAME);

                DataSourceProperty dsProperty = new DataSourceProperty();
                BeanUtils.copyProperties(fromString(dsConfig), dsProperty);
                dsProperty.setPoolName(dsName);

                list.add(dsProperty);
            } catch (Exception e) {
                log.warn("读取数据源配置出错");
                e.printStackTrace();
            }
        }
        return list;
    }

    protected List<DataSourceProperty> queryByTable(Statement statement) throws SQLException {
        Assert.isTrue(existTable(statement, TENANT_DATASOURCE_TABLE_NAME), "未发现租户数据源表:" + TENANT_DATASOURCE_TABLE_NAME);

        List<DataSourceProperty> list = Lists.newArrayList();
        String sql = StrUtil.format("select * from {} where {} = 0",
                TENANT_DATASOURCE_TABLE_NAME,
                COLUMN_DELETED);

        ResultSet rs = statement.executeQuery(sql);
        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();

        while (rs.next()) {
            try {
                JSONObject jsonObj = new JSONObject();

                // 遍历每一列
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i);
                    String value = rs.getString(columnName);
                    jsonObj.putOnce(columnName, value);
                }

                String id = rs.getString(COLUMN_ID);
                String dsName = DynamicDataSourceService.getTenantDsName(id);
                DataSourceProperty dsProperty = new DataSourceProperty();
                DataSourceProperty p = jsonObj.toBean(DataSourceProperty.class);
                BeanUtils.copyProperties(p, dsProperty);
                dsProperty.setPoolName(dsName);

                list.add(dsProperty);
            } catch (Exception e) {
                log.warn("读取数据源配置出错");
                e.printStackTrace();
            }
        }

        return list;
    }

    DataSourceProperty fromString(String dsConfig) {
        return JSONUtil.toBean(dsConfig, DataSourceProperty.class);
    }

    boolean existTable(Statement statement, String table) throws SQLException {
        return QueryUtil.existTable(statement, table);
    }
}
